Conditional Expression DSL
The conditional attributes ([ConditionalFunction], [ConditionalSkill], [ConditionalSubAgent], [ConditionalParameter]) use a simple expression DSL to control visibility at runtime.
Quick Reference
// Simple property (boolean)
[ConditionalFunction("HasAdvancedFeatures")]
// Comparison
[ConditionalFunction("ProviderCount > 1")]
// Boolean logic
[ConditionalFunction("HasTavilyProvider || HasBraveProvider")]
// Complex expression
[ConditionalFunction("(HasSearch || HasBrowse) && IsEnabled")]Supported Syntax
Property Access
The simplest form—checks if a boolean property is true:
[ConditionalFunction("EnableSearch")]
[ConditionalSkill("HasAdvancedFeatures")]
[ConditionalSubAgent("AllowDelegation")]Evaluates to:
return metadata.EnableSearch;Comparison Operators
| Operator | Example | Description |
|---|---|---|
> | Count > 5 | Greater than |
>= | Count >= 5 | Greater than or equal |
< | Size < 1000 | Less than |
<= | Size <= 1000 | Less than or equal |
== | Mode == 'advanced' | Equality |
!= | Status != 'disabled' | Inequality |
[ConditionalFunction("MaxFileSize > 1000000")]
[ConditionalFunction("ProviderCount >= 2")]
[ConditionalFunction("Environment == 'production'")]
[ConditionalFunction("Status != 'disabled'")]Boolean Operators
| Operator | Example | Description |
|---|---|---|
&& | A && B | AND (both must be true) |
|| | A || B | OR (at least one true) |
! | !IsDisabled | NOT (negation) |
// AND - both conditions required
[ConditionalFunction("EnableSearch && AllowAdvanced")]
// OR - any condition sufficient
[ConditionalFunction("HasTavilyProvider || HasBraveProvider || HasBingProvider")]
// NOT - negation
[ConditionalFunction("!IsDisabled")]Parentheses for Grouping
Control operator precedence:
[ConditionalFunction("(HasTavilyProvider || HasBraveProvider) && IsEnabled")]
[ConditionalFunction("(ProviderCount > 1) && (MaxResults >= 10)")]String Literals
Use single quotes for string comparisons:
[ConditionalFunction("Environment == 'production'")]
[ConditionalFunction("Provider != 'none'")]
[ConditionalFunction("Mode == 'advanced'")]Numeric Literals
Numbers work directly:
[ConditionalFunction("MaxValue > 1000")]
[ConditionalFunction("Threshold <= 100")]
[ConditionalFunction("Count == 0")]Property Naming Convention
Properties MUST use PascalCase (start with uppercase letter).
Correct ✓
public class MyMetadata : IToolMetadata
{
public bool EnableSearch { get; set; } // ✓ PascalCase
public int MaxFileSize { get; set; } // ✓ PascalCase
public string ProviderName { get; set; } // ✓ PascalCase
}
[ConditionalFunction("EnableSearch")] // ✓ Works
[ConditionalFunction("MaxFileSize > 1000")] // ✓ WorksIncorrect ✗
public class MyMetadata : IToolMetadata
{
public bool enableSearch { get; set; } // ✗ camelCase
}
[ConditionalFunction("enableSearch")] // ✗ Won't workWhy? The DSL automatically transforms PascalCase identifiers to reference the metadata object. camelCase identifiers are ignored to prevent accidental variable name collisions.
All Four Conditional Attributes
ConditionalFunction
Controls visibility of AIFunctions:
[AIFunction<SearchMetadata>]
[ConditionalFunction("HasAdvancedSearch")]
[AIDescription("Advanced search with filters")]
public async Task<string> AdvancedSearch(string query, SearchFilters filters)
{
// Only visible when metadata.HasAdvancedSearch is true
}ConditionalSkill
Controls visibility of Skills:
[Skill<SearchMetadata>]
[ConditionalSkill("HasMultipleProviders")]
public Skill MultiProviderSearch()
{
// Only visible when metadata.HasMultipleProviders is true
return SkillFactory.Create(...);
}ConditionalSubAgent
Controls visibility of SubAgents:
[SubAgent<DelegationMetadata>]
[ConditionalSubAgent("HasSpecializedAgents")]
public SubAgent SpecializedAnalyzer()
{
// Only visible when metadata.HasSpecializedAgents is true
return SubAgentFactory.Create(...);
}ConditionalParameter
Controls visibility of individual parameters:
[AIFunction<SearchMetadata>]
public async Task<string> Search(
string query,
[ConditionalParameter("HasFilters")]
[AIDescription("Optional search filters")]
SearchFilters? filters = null,
[ConditionalParameter("ProviderCount > 1")]
[AIDescription("Which provider to use")]
string? provider = null)
{
// 'filters' only in schema when HasFilters is true
// 'provider' only in schema when ProviderCount > 1
}Real-World Examples
Multi-Provider Search Tool
public class WebSearchContext : IToolMetadata
{
public bool HasTavilyProvider { get; set; }
public bool HasBraveProvider { get; set; }
public bool HasBingProvider { get; set; }
public int ProviderCount { get; set; }
}
public class WebSearchTool
{
// Available if ANY provider exists
[AIFunction<WebSearchContext>]
[ConditionalFunction("HasTavilyProvider || HasBraveProvider || HasBingProvider")]
public async Task<string> WebSearch(string query) { }
// Only for Tavily (has answer extraction)
[AIFunction<WebSearchContext>]
[ConditionalFunction("HasTavilyProvider")]
public async Task<string> AnswerSearch(string query) { }
// Only for Brave or Bing (have video search)
[AIFunction<WebSearchContext>]
[ConditionalFunction("HasBraveProvider || HasBingProvider")]
public async Task<string> VideoSearch(string query) { }
// Provider selection only when multiple available
[AIFunction<WebSearchContext>]
public async Task<string> Search(
string query,
[ConditionalParameter("ProviderCount > 1")]
string? provider = null) { }
}Feature-Gated Math Tool
public class MathContext : IToolMetadata
{
public bool AllowNegative { get; set; }
public long MaxValue { get; set; }
}
public class MathTool
{
// Only when large values allowed
[AIFunction<MathContext>]
[ConditionalFunction("MaxValue > 1000")]
public long Square(long value) { }
// Only when negatives NOT allowed
[AIFunction<MathContext>]
[ConditionalFunction("AllowNegative == false")]
public long Abs(long value) { }
// Only when negatives ARE allowed
[AIFunction<MathContext>]
[ConditionalFunction("AllowNegative == true")]
public long Subtract(long a, long b) { }
}Environment-Based Tool
public class AppContext : IToolMetadata
{
public string Environment { get; set; } = "development";
public bool IsAdmin { get; set; }
}
public class AdminTool
{
// Only in non-production
[AIFunction<AppContext>]
[ConditionalFunction("Environment != 'production'")]
public void ResetDatabase() { }
// Only for admins in production
[AIFunction<AppContext>]
[ConditionalFunction("Environment == 'production' && IsAdmin")]
public void ViewAuditLogs() { }
}How It Works
Compile-Time
- Source generator extracts the expression string from the attribute
- PascalCase identifiers are transformed to reference the metadata object
- A condition evaluation method is generated
Example: "HasAdvancedSearch" becomes metadata.HasAdvancedSearch
Runtime
- When functions are registered, the condition is evaluated against the metadata
- If
true, the function is included - If
false, the function is hidden from the agent
Null Handling
| Scenario | Result | Reason |
|---|---|---|
| No metadata provided | true (show) | No metadata = show everything |
| Wrong metadata type | false (hide) | Type mismatch = hide |
| Condition evaluates | Result of expression | Normal evaluation |
Security
The DSL has compile-time restrictions to prevent code injection:
Allowed ✓
- Property access:
EnableSearch - Comparisons:
Count > 5 - Boolean logic:
A && B,C || D - Literals:
'string',123 - Grouping:
(A || B) && C
Blocked ✗
- Method calls:
GetType(),ToString() - Reflection:
typeof(),nameof() - System namespaces:
System.IO.* - Dynamic code:
Assembly.Load()
These restrictions are enforced at compile-time by the source generator.
Common Patterns
Feature Flags
[ConditionalFunction("FeatureFlags.NewSearch")]
[ConditionalFunction("FeatureFlags.BetaFeatures && IsInternal")]Subscription Tiers
[ConditionalFunction("Tier == 'premium' || Tier == 'enterprise'")]
[ConditionalFunction("RequestsRemaining > 0")]Environment Gates
[ConditionalFunction("Environment != 'production'")] // Dev only
[ConditionalFunction("Environment == 'production' && IsAdmin")] // Prod adminCapability Detection
[ConditionalFunction("HasGPU && ModelLoaded")]
[ConditionalFunction("DatabaseConnected && HasWriteAccess")]Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Function always hidden | Property is false or metadata not set | Check metadata registration |
| Function always shown | No metadata passed | Ensure .WithTool<T>(metadata) is called |
| Compile error | Invalid expression syntax | Check for typos, use PascalCase |
| Wrong type error | Metadata class mismatch | Use generic attribute [AIFunction<TMetadata>] |
Debug: Check Generated Code
Look in obj/Debug/net8.0/generated/HPD.Agent.SourceGenerator/ for:
{ToolName}Registry.g.cs- Contains generated condition methods
Summary
| Feature | Syntax | Example |
|---|---|---|
| Boolean property | PropertyName | HasFeature |
| Comparison | Property op Value | Count > 5 |
| Equality | Property == 'value' | Mode == 'advanced' |
| AND | A && B | HasA && HasB |
| OR | A || B | HasA || HasB |
| NOT | !Property | !IsDisabled |
| Grouping | (expr) | (A || B) && C |
The conditional DSL provides a safe, type-checked way to dynamically control capability visibility based on runtime context.